package cn.molu.jsoup.service;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import cn.molu.jsoup.util.FileUtil;
import cn.molu.jsoup.util.UrlConstant;
import lombok.extern.slf4j.Slf4j;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;

/**
 * @author 陌路
 * @apiNote 解析资源并下载
 * @date 2024/1/16 14:02
 * @tool Created by IntelliJ IDEA
 */
@Slf4j
@Service("parseHtmlService")
public class ParseHtmlService {
    // pc端
    // https://pic.sogou.com/pics?query=%E7%BE%8E%E5%A5%B3
    // https://pic.sogou.com/pics?mode=6&query=%E6%B2%B3%E6%B5%81

    // 移动端
    // https://pic.sogou.com/pic/index.jsp?v=5
    // https://pic.sogou.com/pic/searchList.jsp?from=homeHotSearch&rcer=&spver=0&keyword=%E7%BE%8E%E5%A5%B3%E5%A3%81%E7%BA%B8

    /**
     * 解析html
     *
     * @param keyword 关键字
     * @return 结果
     */
    public Set<Map<String, String>> parseHtmlByKeyword(String keyword) {
        Document document = Jsoup.parse(HttpUtil.get(UrlConstant.APP_BASE_URL + keyword, 15000));
        String variableName = "window.__INITIAL_STATE__";
        String scriptData = getJsonByScript(document.getElementsByTag("script"), variableName);
        int startIndex = scriptData.indexOf("{");
        int endIndex = scriptData.lastIndexOf("}");
        String jsonData = scriptData.substring(startIndex, endIndex + 1);
        JSONObject jsonObject = JSONUtil.parseObj(jsonData);
        jsonObject = jsonObject.getJSONObject("searchlist");
        jsonObject = jsonObject.getJSONObject("groupFixedResult");
        JSONArray groupPicArray = jsonObject.getJSONArray("groupPic");
        Set<Map<String, String>> list = new HashSet<>();
        getSource(groupPicArray, list);
        String filePath = FileUtil.getFilePath(keyword);
        // 资源数量小于100，直接下载，否则使用多线程下载
        long start = System.currentTimeMillis();
        if (list.size() <= 50) {
            list.forEach(item -> FileUtil.downloadFile(item.get("thumbUrl"), filePath, item.get("title") + item.get("picUrl"), item.get("picUrl"), false));
        } else {
            multiThreaded(new ArrayList<>(list), keyword);
        }
        long time = System.currentTimeMillis() - start;
        log.info("共耗时：{}ms、{}s、{}m", time, (time / 1000), ((time / 1000) / 60));
        FileUtil.write(filePath + "/source", ".json", JSONUtil.toJsonStr(list));
        return list;
    }

    /**
     * 获取json数据
     *
     * @param scriptTags       脚本标签
     * @param jsonVariableName json变量名
     * @return json数据
     */
    private String getJsonByScript(Elements scriptTags, String jsonVariableName) {
        List<String> collects = scriptTags.stream().map(Element::data)
                .filter(data -> data.contains(jsonVariableName))
                .filter(StrUtil::isNotEmpty)
                .collect(Collectors.toList());
        return CollectionUtil.isEmpty(collects) ? "" : collects.get(0);
    }

    /**
     * 解析资源，获取资源数据
     *
     * @param jsonArray 资源数据集
     * @param list      结果数据集
     */
    private void getSource(JSONArray jsonArray, Set<Map<String, String>> list) {
        for (Object object : jsonArray) {
            if (object instanceof JSONArray item) {
                getSource(item, list);
            }
            if (object instanceof JSONObject item) {
                var map = new HashMap<String, String>();
                map.put("thumbUrl", item.getStr("thumbUrl"));
                map.put("title", item.getStr("title"));
                map.put("pageUrl", item.getStr("pageUrl"));
                map.put("picUrl", item.getStr("picUrl"));
                list.add(map);
            }
        }
    }

    /**
     * 多线程下载
     *
     * @param list 数据集
     */
    private void multiThreaded(List<Map<String, String>> list, String keyword) {
        int threadCount = 10; // 线程数量
        ExecutorService executorService = Executors.newFixedThreadPool(threadCount);
        List<Future<Void>> futures = new ArrayList<>();
        // 计算每个任务的起始和结束索引
        int chunkSize = (list.size() + threadCount - 1) / threadCount; // 向上取整，确保数据分配均匀
        for (int i = 0; i < threadCount; i++) {
            int start = i * chunkSize;
            int end = Math.min(start + chunkSize, list.size());
            List<Map<String, String>> maps = list.subList(start, end);
            // 创建并提交任务
            Callable<Void> task = () -> {
                FileUtil.downloadFile(FileUtil.getFilePath(keyword), maps);
                return null;
            };
            Future<Void> future = executorService.submit(task);
            futures.add(future);
        }
        // 等待所有任务完成，并处理异常
        for (Future<Void> future : futures) {
            try {
                future.get();
            } catch (Exception e) {
                log.error("线程执行出错：" + e.getMessage());
            }
        }
        // 关闭线程池
        executorService.shutdown();
        log.info("共下载了：{}个文件（任务总数：{}）", list.size(), futures.size());
    }
}
